package com.empoweragile.web.filter_bullet;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * Initial parameters: "Expires", "max-age" and
 * "s-maxage". Value: time-to-live in seconds. 
 * 
 * Use this filter to control caching of resources in your application.
 * Especially useful for static resources like html, images and JavaScript
 * files or dynamic resources that change with known periodicity 
 * The caching is performed by setting appropriate html headers in html
 * response.
 *  
 * <p>
 * 
 * Caching filter can be especially useful for simplifying J2EE deployment 
 * process. Instead distributing static resorce to Http server (or dedicated
 * reverse proxy) during application deployment, with Caching filter you can 
 * rely on the reverse proxy to cache the resource in JIT manner. This way, 
 * there is no need to deploy copies of static resource files to http servers
 * or reverse proxies.  
 * 
 * </p>
 *  
 * <p>
 * 
 * In order for resource to be cached by reverse proxy, the resource
 * time-to-live value can be relatively small, for example 5 seconds, and still
 * important performance benefits can be obtained at high volume sites.
 * 
 * </p>
 * 
 * <p>
 * 
 * Resources over which filter is applied are controlled by url-pattern
 * parameter in filter mapping. The filter adds the "Expires", "s-maxage" and
 * "max-age" HTTP header to HTTP response whose value is calculated on base of
 * initial parameters of the filter. s-maxage header is used to set time the
 * resource can be kept in reverse proxy cache. maxage header is used by browser
 * cache as well as Expires header (maxage overrides Expires header). For more
 * information on headers take a look at <a
 * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">HTTP Protocol 
 * Specification</a> Filter initial parameters named: "Expires". "max-age" and
 * "s-maxage" indicates time-to-live of a resource in seconds. In case of
 * "Expires" parameter, init-param is used to calculate the value based on
 * current time plus the value of "Expires" int-param. If no value entered,
 * default value of 5 seconds assumed.
 * 
 * </p>
 * 
 * <p>
 * 
 * Note: Http caching does not work over https, so do not set this filter for
 * secured content. (Some browsers might cache the https content, depending on
 * configuration.) However, if the communication between reverse proxy and web
 * server is not secured (typically inside DMZ), then makes sense setting the
 * filter even if resources are served over https from reverse proxy outwards.
 * 
 * Also, be sure to maintain your application sever time correct, otherwise
 * Expires header can be given wrong value.
 * 
 * </p>
 */

public class CachingFilter implements Filter {

	static final String cacheControlHttpHeader = "Cache-Control: public,";

	static final String timePatternForExpiresHeader = "EEE, dd MMM yyyy HH:mm:ss z";

	static final String expiresHttpHeader = "Expires";

	static final String max_ageHttpHeader = "max_age";

	static final String s_maxageHttpHeader = "s_maxage";

	protected static Log logger = LogFactory
			.getLog(CachingFilter.class);

	private FilterConfig filterConfig;

	private int max_Age;

	private int s_MaxAge;

	private int expires;

	/**
	 * Default value for http headers set with this filter in seconds: 5. This
	 * value is assumed if no value entered in filter configuration as
	 * init-param in web.xml file.
	 */

	private static int defaultTimeToLive = 5;

	/**
	 * 
	 * @param request
	 *            The servlet request we are processing
	 * @param response
	 *            The servlet response we are creating
	 * @param chain
	 *            The filter chain we are processing
	 * 
	 * @exception IOException
	 *                if an input/output error occurs
	 * @exception ServletException
	 *                if a servlet error occurs
	 */

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		addCacheHeaders(request, response);
		logger.info("Caching headers applied: " + this.toString());
		chain.doFilter(request, response);

	}

	private void addCacheHeaders(ServletRequest request,
	ServletResponse response) throws IOException, ServletException {

		HttpServletResponse httpResponse = (HttpServletResponse) response;
		httpResponse.setHeader(expiresHttpHeader + ": ", calculateExpires());
		httpResponse.setHeader(cacheControlHttpHeader, max_ageHttpHeader
		+ "=" + Integer.toString(max_Age) + ", " + s_maxageHttpHeader
		+ "=" + Integer.toString(s_MaxAge));
		
	}

	private String calculateExpires() {

		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.SECOND, expires);
		String expiresHttpHeaderTimePattern = timePatternForExpiresHeader;
		SimpleDateFormat sdf = new SimpleDateFormat(
				expiresHttpHeaderTimePattern);
		sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
		return sdf.format(calendar.getTime());

	}

	/**
	 * Init method for this filter
	 * 
	 */
	public void init(FilterConfig filterConfig) {
		
		this.filterConfig = filterConfig;		
		readInitParameters();
		
	}

	/**
	 * 
	 * Destroy method for this filter anulls filter configuration
	 * 
	 */
	public void destroy() {

		this.filterConfig = null;

	}

	/**
	 * 
	 * Time-to-live obtained from filter initial parameter named "TTL". Value
	 * 
	 * measurement is in seconds.
	 */

	private void readInitParameters() {

		readExpiresHeader();
		readMax_AgeHeader();
		readS_MaxAgeHeader();

	}

	private void readExpiresHeader() {

		if (parameterUndefined(expiresHttpHeader)) {
			expires = defaultTimeToLive;
			logger
					.warn("No initial parameter set for 'Expires' header. Default value used: "
							+ new Integer(defaultTimeToLive).toString());
			return;
		}
		
		expires = new Integer(filterConfig.getInitParameter(expiresHttpHeader))
		.intValue();

	}

	private void readMax_AgeHeader() {

		if (parameterUndefined(max_ageHttpHeader)) {
			max_Age = defaultTimeToLive;
			logger.warn("No initial parameter set for 'max_age' header. Default value used: "
							+ new Integer(defaultTimeToLive).toString());
			return;
		}

		max_Age = new Integer(filterConfig.getInitParameter(max_ageHttpHeader))
		.intValue();

	}

	private void readS_MaxAgeHeader() {
		if (parameterUndefined(s_maxageHttpHeader)) {
			s_MaxAge = defaultTimeToLive;
			logger
					.warn("No initial parameter set for 's_maxage' header. Default value used: "
							+ new Integer(defaultTimeToLive).toString());
			return;
		}
		s_MaxAge = new Integer(filterConfig
		.getInitParameter(s_maxageHttpHeader)).intValue();
	}

	private boolean parameterUndefined(String parameterName) {
		return filterConfig.getInitParameter(parameterName) == null
		|| filterConfig.getInitParameter(expiresHttpHeader).equals("");
	}

	/**
	 * 
	 * Return a String representation of this object.
	 */

	@Override
	public String toString() {
		
		if (filterConfig == null) {
			return ("CachingFilter()");
		}

		StringBuffer buffer = new StringBuffer("CachingFilter: ");
		buffer.append(filterConfig);
		buffer.append(" ");
		return (buffer.toString());

	}

}